package com.yixing.tech.entity.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.yixing.tech.common.base.BaseMongoDao;
import com.yixing.tech.common.base.BasePO;
import com.yixing.tech.common.base.BaseVO;
import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.base.ConditionsVO;
import com.yixing.tech.common.base.Page;
import com.yixing.tech.common.base.PageRow;
import com.yixing.tech.common.enums.ErrCode;
import com.yixing.tech.common.enums.YesOrNo;
import com.yixing.tech.common.utils.CommonUtil;
import com.yixing.tech.common.utils.DataUtil;
import com.yixing.tech.common.utils.DateUtil;
import com.yixing.tech.common.utils.ExcelUtil;
import com.yixing.tech.common.vo.s3.ButtonVO;
import com.yixing.tech.common.vo.s3.RoleVO;
import com.yixing.tech.common.vo.s3.UserVO;
import com.yixing.tech.common.vo.s3.WebPageVO;
import com.yixing.tech.common.vo.s5.EntitySchema;
import com.yixing.tech.common.vo.s5.NodeVO;
import com.yixing.tech.entity.config.EntityThreadConfig;
import com.yixing.tech.entity.entity.CustomActionParam;
import com.yixing.tech.entity.entity.GroupParam;
import com.yixing.tech.entity.entity.RequestType;
import com.yixing.tech.entity.entity.attr.AttrType;
import com.yixing.tech.entity.entity.attr.MultiObject;
import com.yixing.tech.entity.entity.attr.ObjArray;
import com.yixing.tech.entity.entity.attr.TimeText;
import com.yixing.tech.entity.po.EntityData;
import com.yixing.tech.entity.po.Obj;
import com.yixing.tech.entity.service.EntityBaseService;
import com.yixing.tech.entity.service.EntityHookServiceManager;
import com.yixing.tech.entity.service.IEntityService;
import com.yixing.tech.entity.utils.EntityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.modelmapper.TypeToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationExpression;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.ConvertOperators;
import org.springframework.data.mongodb.core.aggregation.DateOperators;
import org.springframework.data.mongodb.core.aggregation.GraphLookupOperation;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.aggregation.LimitOperation;
import org.springframework.data.mongodb.core.aggregation.SkipOperation;
import org.springframework.data.mongodb.core.aggregation.SortOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.schema.JsonSchemaObject;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author yixing tech
 * at 2022/04/08
 * describe：
 */
@Slf4j
@Service
public class EntityBaseServiceImpl implements EntityBaseService {

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private BaseMongoDao baseMongoDao;
    @Autowired
    private ObjProcessor objProcessor;
    @Autowired
    private IEntityService entityService;
    @Autowired
    private EntityHookServiceManager entityHookServiceManager;

    @PostConstruct
    public void init() {
        EntityThreadConfig.commSubExecutor.allowCoreThreadTimeOut(true);
        EntityThreadConfig.commExecutor.allowCoreThreadTimeOut(true);
    }

    @Override
    public void getImportTemplate(HttpServletResponse response, String templateId, String attrCode, UserVO userVO) throws BusinessException {
        EntitySchema cond = queryByTableId(templateId, userVO);
        if (ObjectUtils.isEmpty(attrCode)) {
            List<EntitySchema.AttrConfig> formConfigs = cond.getFormConfig();
            List<String> titles = new ArrayList<>();
            for (EntitySchema.AttrConfig m : formConfigs) {
                String attrName = cond.getAttrName(m.getCode());
                titles.add(attrName);
            }
            ExcelUtil.buildExcelTemplate(response, cond.getName() + ".xls", titles);
        } else {
            List<EntitySchema.AttrVO> attrs = cond.getAttrs();
            Optional<EntitySchema.AttrVO> first = attrs.stream().filter(m -> m.getCode().equals(attrCode)).findFirst();
            if (!first.isPresent()) throw new BusinessException(-1, "attrCode不存在");
            List<EntitySchema.AttrVO> subAttrVOs = DataUtil.o2o(first.get().getReference(), new TypeToken<List<EntitySchema.AttrVO>>() {
            }.getType());
            List<String> titles = subAttrVOs.stream().map(m -> m.getName()).collect(Collectors.toList());
            ExcelUtil.buildExcelTemplate(response, cond.getName() + "-" + attrCode + ".xls", titles);
        }
    }

    @Override
    public String insetEntity(String templateId, EntityData entity) throws BusinessException {
        return insetEntity(templateId, entity, true);
    }

    @Override
    public String insetEntity(String templateId, EntityData entity, boolean hasCheck) throws BusinessException {
        if (!entity.containsKey("tenantId")) throw new BusinessException(-1, "Miss TenantId");
        entity.put("isDelete", YesOrNo.NO.ordinal());
        EntitySchema cond = queryByTableId(templateId);

        Boolean enablePreHook = cond.getEnablePreHook();
        if (enablePreHook != null && enablePreHook) {
            entity = entityHookServiceManager.beforeInsert(templateId, entity);
        }

        List<EntitySchema.AttrVO> attrs = cond.getAttrs();
        Map<String, String> dictMap = new HashMap<>(5);
        for (String key : entity.keySet()) {
            //校验唯一和必填
            EntitySchema.AttrConfig attr = objProcessor.getAttrConfig(key, cond.getAddConfig());
            if (attr == null) continue;
            //空串置空
            if (ObjectUtils.isEmpty(entity.get(key))) entity.put(key, null);
            Boolean isUnique = attr.getIsUnique();
            if (isUnique != null && isUnique) {
                Object value = entity.get(key);
                boolean exists = mongoTemplate.exists(Query.query(Criteria.where(key).is(value).and("isDelete").ne(YesOrNo.YES.ordinal())), objProcessor.getTableName(templateId));
                if (exists) throw new BusinessException(ErrCode.ENTITY_DUPLICATION, value.toString());
            }
        }
        EntityData savePre = new EntityData();
        savePre.putAll(entity);
        String splitCode = objProcessor.parserValue(entity, attrs, false, false, dictMap, null, hasCheck);
        if (!ObjectUtils.isEmpty(splitCode)) {
            //根据字段，拆分数据，批量新增
            List splitValues = entity.get(splitCode, List.class);
            List<EntityData> entities = new ArrayList<>();
            for (Object o : splitValues) {
                EntityData map = new EntityData();
                map.putAll(entity);
                map.put(splitCode, o);
                entities.add(map);
            }
            List<EntityData> rs = (List<EntityData>) mongoTemplate.insert(entities, objProcessor.getTableName(templateId));
            entity = rs.get(0);
        } else {
            EntityUtils.fillUserInfo(entity);
            entity = mongoTemplate.save(entity, objProcessor.getTableName(templateId));
            objProcessor.fillObjList(savePre, entity, attrs);
        }
        objProcessor.putBackRefValue(entity, attrs, false);
        Boolean enableAfterHook = cond.getEnableAfterHook();
        if (enableAfterHook != null && enableAfterHook) {
            entity = entityHookServiceManager.afterInsert(templateId, entity);
        }
        return entity.get("_id").toString();
    }


    @Override
    public void insetEntitys(String templateId, List<Map> list, UserVO loginUser) throws BusinessException {
        if (ObjectUtils.isEmpty(list)) return;
        EntitySchema cond = queryByTableId(templateId);
        Boolean enablePreHook = cond.getEnablePreHook();
        if (enablePreHook != null && enablePreHook) {
            //TODO 疑似缺陷
            entityHookServiceManager.beforeInsert(templateId, null);
        }
        List<EntitySchema.AttrVO> attrs = cond.getAttrs();
        Map<String, String> dictMap = new HashMap<>(5);
        for (Map map : list) {
            objProcessor.fillCreateUser(map, loginUser);
            map.put("isDelete", YesOrNo.NO.ordinal());
            objProcessor.parserValue(map, attrs, false, false, dictMap, null);
        }
        //膜厚反射率绑定规格线
        if (templateId.equals("656c4446572ccb5ca8815161")) {
            dmParserData(list);
        }else if(templateId.equals("687a17fe387bd10bffe76c52")){
            jzParserData(list);
        }else if(templateId.equals("68f727fa87e4e87e8da7c54f")){
            // 湿重
            szParserData(list);
        }
        mongoTemplate.insert(list, objProcessor.getTableName(templateId));
        objProcessor.putBackRefValue(list.get(0), attrs, false);

    }


    /**
     * 膜厚反射率绑定规格线
     *
     * @param list
     */
    private void dmParserData(List<Map> list) {
        if (ObjectUtils.isEmpty(list)) return;
        Map entity = list.get(0);
        String process = entity.get("工序").toString();
        String processId = "正膜".equals(process) ? "64cb54ee9b8f1972c0e0f8c2" : "64cb54f6f607ea2da1f86fcf";
        //查询膜厚和反射率规格线
        List<Document> docs = mongoTemplate.find(Query.query(Criteria.where("工序.id").is(new ObjectId(processId)).and("isDelete").ne(1)), Document.class, "entity_64d5d99161059d3c5b5c1ad2");
        if (ObjectUtils.isEmpty(docs)) return;
        Document mh = null;
        Document fsl = null;
        for (Document doc : docs) {
            String item = doc.getString("检查项名称");
            if (item.contains("膜厚")) {
                mh = doc;
            } else if (item.contains("折射率")) {
                fsl = doc;
            }
        }
        for (Map m : list) {
            m.put("膜厚规格线", mh);
            m.put("折射率规格线", fsl);
        }
    }

    /**
     * 减重绑定规格线
     *
     * @param list
     */
    private void jzParserData(List<Map> list) {
        if (ObjectUtils.isEmpty(list)) return;
        Map<String, Document> temp = new HashMap<>(2);
        for (Map m : list) {
            m.put("valueTime", new Date());
            String process = m.get("工序").toString();
            Document doc = null;
            if (temp.containsKey(process)) {
               doc = temp.get(process);
            } else {
                String processId = "制绒".equals(process) ? "64cb54619b8f1972c0e0f8bd" : "64cb54b79b8f1972c0e0f8bf";
                doc = mongoTemplate.findOne(Query.query(Criteria.where("工序.id").is(new ObjectId(processId)).and("isDelete").ne(1).and("检查项名称").is("SPC-减重")), Document.class, "entity_64d5d99161059d3c5b5c1ad2");
                if (ObjectUtils.isEmpty(doc)) return;
                temp.put(process, doc);
            }
            m.put("规格线", doc);
            m.put("process",doc.get("工序"));
        }
    }

    /**
     * 湿重绑定规格线
     *
     * @param list
     */
    private void szParserData(List<Map> list) {
        if (ObjectUtils.isEmpty(list)) return;
        Document doc = null;
        doc = mongoTemplate.findOne(Query.query(Criteria.where("工序.id").is(new ObjectId("64cb54fd9b8f1972c0e0f8c3"))
                        .and("isDelete").ne(1).and("检查项名称").is("SPC-湿重")),
                Document.class, "entity_64d5d99161059d3c5b5c1ad2");
        Document processDoc = new Document();
        processDoc.put("id", new ObjectId("64cb54fd9b8f1972c0e0f8c3"));
        if (ObjectUtils.isEmpty(doc)) return;
        for (Map m : list) {
            m.put("valueTime", new Date());
            m.put("规格线", doc);
            m.put("工序", "丝网");
            m.put("process", processDoc);
        }
    }

    @Override
    public void importEntity(String templateId, MultipartFile file, UserVO loginUser) throws Exception {
        EntitySchema cond = queryByTableId(templateId);
        List<EntitySchema.AttrVO> attrs = cond.getAttrs();
        List<Map<String, Object>> list = ExcelUtil.excelToList(file, attrs);
        Map<String, String> dictMap = new HashMap<>(5);
        label1:
        for (Map<String, Object> map : list) {
            //校验唯一和必填
            //联合主键
            Map<String, Object> uniques = new HashMap<>();
            for (String key : map.keySet()) {
                EntitySchema.AttrConfig attr = objProcessor.getAttrConfig(key, cond.getFormConfig());
                if (attr == null) continue;
                Boolean isNecessary = attr.getIsNecessary();
                if (isNecessary != null && isNecessary) {
                    Object value = map.get(key);
                    if (null == value || value.toString().length() == 0)
                        throw new BusinessException(ErrCode.PARAMETER_ERROR);
                }
                Boolean isUnique = attr.getIsUnique();
                if (isUnique != null && isUnique) {
                    Object value = map.get(key);
                    AttrType type = objProcessor.getAttrType(key, attrs);
                    if (type instanceof TimeText) {
                        value = value instanceof String ? DateUtil.praseDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), value.toString()) : value;
                    }
                    uniques.put(key, value);
                }
            }
            if (!ObjectUtils.isEmpty(uniques)) {
                Criteria criteria = Criteria.where("isDelete").ne(YesOrNo.YES.ordinal());
                for (Map.Entry<String, Object> values : uniques.entrySet()) {
                    criteria.and(values.getKey()).is(values.getValue());
                }
                mongoTemplate.findAllAndRemove(Query.query(criteria), objProcessor.getTableName(templateId));
            }
            objProcessor.fillCreateUser(map, loginUser);
            map.put("isDelete", YesOrNo.NO.ordinal());
            objProcessor.parserValue(map, attrs, false, true, dictMap, null);
            mongoTemplate.save(map, objProcessor.getTableName(templateId));
            objProcessor.putBackRefValue(map, attrs, false);
        }

    }

    @Override
    public void exportEntity(HttpServletResponse response, String templateId, ConditionsVO condition, UserVO userVO) throws BusinessException {
        List<AggregationOperation> operations = new ArrayList<>();
        EntitySchema cond = queryByTableId(templateId);
        List<EntitySchema.AttrVO> attrs = cond.getAttrs();
        objProcessor.parseConditions(condition, attrs);
        objProcessor.fillConditionConfig(templateId, condition, userVO);
        List<Criteria> criteriaList = baseMongoDao.condition2CriteriaList(condition);
        Criteria criteria = new Criteria().andOperator(criteriaList);
        operations.add(Aggregation.match(criteria));


        try {
            Sort sort;
            List<BaseVO.Sort> bSorts = condition.getSortConditions();
            if (ObjectUtils.isEmpty(bSorts)) bSorts = cond.getSortConditions();
            if (ObjectUtils.isEmpty(bSorts)) {
                BaseVO.Sort bSort = condition.getSortCondition();
                if (bSort == null) bSort = cond.getSortCondition();
                sort = Sort.by(Sort.Direction.fromString(bSort.getDirection()), bSort.getCode());
            } else {
                sort = Sort.by(bSorts.stream().map(m -> new Sort.Order(Sort.Direction.fromString(m.getDirection()), m.getCode())).collect(Collectors.toList()));
            }
            SortOperation sortOperation = Aggregation.sort(sort);
            operations.add(sortOperation);
        } catch (Exception e) {
        }


        List<EntitySchema.AttrConfig> titleConfig = cond.getTitleConfig();
        objProcessor.fillNotReadAttrConfig(templateId, titleConfig, userVO);
        objProcessor.projectAttr(operations, titleConfig, cond);

        Aggregation agg = Aggregation.newAggregation(operations);
        AggregationResults<Document> results = mongoTemplate.aggregate(agg, objProcessor.getTableName(templateId), Document.class);
        List<Document> list = results.getMappedResults();
        Collections.sort(attrs);
        //多选关联对象，数据填充
        for (EntitySchema.AttrVO attr : attrs) {
            AttrType type = AttrType.build(attr.getType());
            if (type instanceof ObjArray) break;
            if (type instanceof MultiObject) {
                //配置填充
                String refObjCode = attr.getReference().toString();

                EntitySchema refObj = queryByTableId(refObjCode);
                if (refObj == null) continue;
                attr.setReference(refObj.getAttrs());
                //值填充
                for (Document doc : list) {
                    List<String> ids = doc.getList(attr.getCode(), String.class);
                    List<ConditionsVO.Condition> conditionlist = new ArrayList<>();
                    ConditionsVO.Condition conditions = new ConditionsVO.Condition();
                    conditions.setKey("_id");
                    conditions.setSymbol("in");
                    conditions.setValue(ids);
                    conditionlist.add(conditions);
                    ConditionsVO c = new ConditionsVO();
                    c.setConditions(conditionlist);
                    doc.put(attr.getCode(), list(c, refObjCode, null));
                }
                break;
            }
        }
        ExcelUtil.exportEntityToExcel(response, cond.getName() + ".xls", list, attrs);
    }

    /**
     * 解析excel
     *
     * @param file
     * @param templateId
     * @return
     * @throws BusinessException
     */
    @Override
    public Object parseExcel(MultipartFile file, String templateId, String tenantId) throws Exception {
        EntitySchema cond = queryByTableId(templateId);
        List<EntitySchema.AttrVO> attrs = cond.getAttrs();
        return parseEntityExcel(file, attrs, tenantId);
    }

    @Override
    public Object parseSubExcel(MultipartFile file, String templateId, String attrName, String tenantId) throws Exception {
        if (ObjectUtils.isEmpty(templateId) || "null".equals(templateId)) {
            return ExcelUtil.excelToList(file);
        }
        EntitySchema cond = queryByTableId(templateId);
        List<EntitySchema.AttrVO> attrs = cond.getAttrs();
        Map<String, EntitySchema.AttrVO> attrVoMap = attrs.stream().collect(Collectors.toMap(EntitySchema.AttrVO::getCode, a -> a));
        if (attrVoMap.containsKey(attrName)) {
            EntitySchema.AttrVO attrVO = attrVoMap.get(attrName);
            if (!attrVO.getType().equals("objArray")) {
                throw new BusinessException("属性不是子表单属性");
            }
            List<EntitySchema.AttrVO> attrVOList = ((ArrayList<Map>) attrVO.getReference()).stream().map(m -> {
                JSONObject json = new JSONObject(m);
                return JSONObject.parseObject(json.toJSONString(), EntitySchema.AttrVO.class);
            }).collect(Collectors.toList());
            return parseEntityExcel(file, attrVOList, tenantId);
        } else {
            throw new BusinessException("子表单属性不存在");
        }
    }

    private Object parseEntityExcel(MultipartFile file, List<EntitySchema.AttrVO> attrs, String tenantId) throws Exception {
        Map<String, EntitySchema.AttrVO> attrVoMap = attrs.stream().collect(Collectors.toMap(EntitySchema.AttrVO::getCode, a -> a));
        List<Map<String, Object>> list = ExcelUtil.excelToList(file, attrs);
        Map<String, AttrType> attrTypeCache = new HashMap<>();
        list.forEach(map -> {
            map.forEach((k, v) -> {
                // 属性 -> 属性类型
                if (attrVoMap.containsKey(k)) {
                    AttrType attrType = attrTypeCache.computeIfAbsent(k, k1 -> AttrType.build(attrVoMap.get(k).getType()));
                    try {
                        map.put(k, attrType.wrapValue(attrVoMap.get(k), v, mongoTemplate, this, tenantId));
                    } catch (BusinessException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        });
        return list;
    }


    @Override
    public void removeEntity(String templateId, String entityId, String tenantId) throws BusinessException {
        EntitySchema objVO = queryByTableId(templateId);
        Boolean enablePreHook = objVO.getEnablePreHook();
        if (enablePreHook != null && enablePreHook) {
            entityHookServiceManager.beforeDelete(templateId, entityId);
        }
        Update update = new Update();
        update.set("isDelete", YesOrNo.YES.ordinal());
        mongoTemplate.upsert(Query.query(Criteria.where("_id").is(new ObjectId(entityId))), update, objProcessor.getTableName(templateId));
        Boolean enableAfterHook = objVO.getEnableAfterHook();
        if (enableAfterHook != null && enableAfterHook) {
            entityHookServiceManager.afterDelete(templateId, entityId);
        }
    }

    /**
     * 批量删除实体
     *
     * @param templateId
     * @param entityIds
     * @throws BusinessException
     */
    @Override
    public void removeEntities(String templateId, List<String> entityIds) throws BusinessException {
        if (ObjectUtils.isEmpty(entityIds)) return;
        Update update = new Update();
        update.set("isDelete", YesOrNo.YES.ordinal());
        mongoTemplate.updateMulti(Query.query(Criteria.where("_id").in(entityIds.stream().map(ObjectId::new).collect(Collectors.toList()))), update, objProcessor.getTableName(templateId));
    }

    @Override
    public void removeEntity(String templateId, Map<String, Object> map, String tenantId) throws BusinessException {
        Criteria criteria = new Criteria();
        List<Criteria> list = new ArrayList<>();
        list.add(Criteria.where("tenantId").is(tenantId));
        if (map != null)
            for (String key : map.keySet()) {
                list.add(Criteria.where(key).is(map.get(key)));
            }
        criteria.andOperator(list);
        mongoTemplate.remove(Query.query(criteria), objProcessor.getTableName(templateId));
    }

    @Override
    public Document getEntity(String templateId, String entityId, UserVO userVO, boolean forEdit) throws BusinessException {
        List<AggregationOperation> operations = new ArrayList<>();
        Criteria criteria = Criteria.where("_id").is(entityId);
        operations.add(Aggregation.match(criteria));
        EntitySchema cond = queryByTableId(templateId);
        List<EntitySchema.AttrConfig> matchConfig = forEdit ? cond.getFormConfig() : cond.getPreviewConfig();
        objProcessor.fillNotReadAttrConfig(templateId, matchConfig, userVO);
        objProcessor.projectAttr(operations, matchConfig, cond);
        Aggregation agg = Aggregation.newAggregation(operations);
        String tableName = objProcessor.getTableName(templateId);
        AggregationResults<Document> results = mongoTemplate.aggregate(agg, tableName, Document.class);
        List<Document> list = results.getMappedResults();
        if (CommonUtil.listIsBlank(list)) throw new BusinessException(ErrCode.RESULT_EMPTY);
        Document document = list.get(0);
        return document;
    }

    @Override
    public void updateEntity(String templateId, EntityData entity) throws BusinessException {
        EntitySchema cond = queryByTableId(templateId);
        Boolean enablePreHook = cond.getEnablePreHook();
        if (enablePreHook != null && enablePreHook) {
            entity = entityHookServiceManager.beforeUpdate(templateId, entity);
        }
        Object id = entity.remove("id");
        if (null == id) throw new BusinessException(ErrCode.PARAMETER_ERROR);
        List<EntitySchema.AttrVO> attrs = cond.getAttrs();
        for (String key : entity.keySet()) {
            //校验唯一和必填
            EntitySchema.AttrConfig attr = objProcessor.getAttrConfig(key, cond.getFormConfig());
            if (attr == null) continue;
//            //空串置空
//            if (ObjectUtils.isEmpty(entity.get(key))) entity.put(key, null);
            Boolean isUnique = attr.getIsUnique();
            if (isUnique != null && isUnique) {
                Object value = entity.get(key);
                boolean exists = mongoTemplate.exists(Query.query(Criteria.where(key).is(value)
                        .and("_id").ne(new ObjectId(id.toString()))
                        .and("isDelete").ne(YesOrNo.YES.ordinal())), objProcessor.getTableName(templateId));
                if (exists) throw new BusinessException(ErrCode.ENTITY_DUPLICATION, value.toString());
            }
        }
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(id.toString()));
        EntityData updateDoc = mongoTemplate.findOne(query, EntityData.class, objProcessor.getTableName(templateId));
        EntityData oldData = new EntityData();
        oldData.putAll(updateDoc);
        Map temp = new HashMap(entity);
        objProcessor.parserValue(entity, attrs, true, false, null, updateDoc, true);
        Update update = new Update();
        update.set("updateTime", new Date());
        for (Object key : entity.keySet()) {
            Object value = entity.get(key);
            if (null == value) continue;
            if (value.toString().length() == 0)
                update.unset(key.toString());
            else
                update.set(key.toString(), value);
        }
        beforeUpdateBiz(templateId, entity, updateDoc);
        mongoTemplate.upsert(query, update, objProcessor.getTableName(templateId));
        try {
            for (Object key : temp.keySet()) {
                Object value = temp.get(key);
                if (null == value) continue;
                updateDoc.put(key.toString(), value);
            }
            objProcessor.putBackRefValue(updateDoc, attrs, true);
        } catch (Exception e) {
            log.error("-----exception----", e.getCause());
            throw new BusinessException(ErrCode.PARAMETER_ERROR, e.getMessage());
        }
        Boolean enableAfterHook = cond.getEnableAfterHook();
        if (enableAfterHook != null && enableAfterHook) {
            entityHookServiceManager.afterUpdate(templateId, oldData, updateDoc);
        }
    }


    private void beforeUpdateBiz(String templateId, EntityData entity, Document oldDoc) throws BusinessException {
        if("68f1f7e29611b117745d5ae4".equals(templateId)) {
            Boolean isSync = oldDoc.getBoolean("是否已同步");
            if(Objects.equals(true, isSync)) {
                throw new BusinessException("已经同步的单据不能修改!");
            }
            Object store, storeId;
            if(entity != null && (store = entity.get("仓库")) instanceof Document && (storeId = ((Document)store).get("id")) != null) {
                if(!Objects.equals("68f9a0f087e4e87e8da7ea0c", storeId.toString())) {
                    throw new BusinessException("仓库固定是码垛成品库，不能修改!");
                }
            }
        }
    }

    /**
     * 更新实体
     *
     * @param templateId
     * @param entity
     * @throws BusinessException
     */
    @Override
    @Async
    public void updateEntityAsync(String templateId, EntityData entity) throws BusinessException {
        updateEntity(templateId, entity);
    }

    /**
     * 更新实体
     *
     * @param templateId
     * @param cond
     * @throws BusinessException
     */
    @Override
    public void updateEntity(String templateId, List<ConditionsVO.Condition> cond, Map map) throws BusinessException {
        ConditionsVO conditionsVO = new ConditionsVO();
        conditionsVO.setTenantId(map.get("tenantId").toString());
        conditionsVO.setConditions(cond);
        Criteria criteria = new Criteria().andOperator(baseMongoDao.condition2CriteriaList(conditionsVO));
        Query query = new Query(criteria);
        EntityData entity = new EntityData();
        for (Object key : map.keySet()) {
            Object value = map.get(key);
            if (null == value) continue;
            entity.put(key.toString(), value);
        }
        Document document = mongoTemplate.findOne(query, Document.class, objProcessor.getTableName(templateId));
        if (document != null) {
            entity.put("id", document.getString("id"));
            updateEntity(templateId, entity);
        }
    }

    @Override
    public void disableEntity(String templateId, String entityId, LinkedHashMap updateMap) throws BusinessException {
        Update update = new Update();
        update.set("isDelete", YesOrNo.DISABLE.ordinal());
        if (!CommonUtil.mapIsBlank(updateMap)) {
            for (Object key : updateMap.keySet()) {
                Object value = updateMap.get(key);
                if (null == value) continue;
                update.set(key.toString(), value);
            }
        }
        mongoTemplate.upsert(Query.query(Criteria.where("_id").is(new ObjectId(entityId))), update, objProcessor.getTableName(templateId));
        /**
         * 领料申请拒绝时，释放锁定库存
         */
        if ("634527758116bd5315fa1429".equals(templateId)) {
            freeCount(entityId, templateId);
        }
        if ("640ac805d7ba203756091e92".equals(templateId)) {
            freeListCount(entityId, templateId);
        }
    }

    @Async
    public void freeCount(String entityId, String templateId) {
        Document document = mongoTemplate.findById(entityId, Document.class, objProcessor.getTableName(templateId));
        double count = Double.valueOf(document.getOrDefault("count", 0).toString());
        Update update1 = new Update();
        update1.inc("锁定数量", -count);
        mongoTemplate.upsert(Query.query(Criteria.where("_id").is(new ObjectId(document.get("选择物料", Document.class).getString("id")))), update1, objProcessor.getTableName("63451d708116bd5315fa1422"));
    }

    @Async
    public void freeListCount(String entityId, String templateId) {
        Document document = mongoTemplate.findById(entityId, Document.class, objProcessor.getTableName(templateId));
        List<Document> subList = document.getList("领料清单", Document.class);
        if (CommonUtil.listIsBlank(subList)) return;
        subList.forEach(v -> {
            double count = Double.valueOf(v.getOrDefault("领料数量", 0).toString());
            Update update1 = new Update();
            update1.inc("锁定数量", -count);
            mongoTemplate.upsert(Query.query(Criteria.where("_id").is(new ObjectId(v.getString("选择库存")))), update1, objProcessor.getTableName("63451d708116bd5315fa1422"));
        });
    }

    @Override
    public void excAction(CustomActionParam param) throws BusinessException {
        String templateId = param.getObjCode();
        String entityId = param.getEntityId();
        if (StringUtils.isEmpty(entityId) || StringUtils.isEmpty(templateId))
            throw new BusinessException(ErrCode.MISS_CONFIG);
        String actionCode = param.getMethod();
        EntitySchema cond = queryByTableId(templateId);
        List<EntitySchema.AttrVO> attrs = cond.getAttrs();
        AttrType type = objProcessor.getAttrType(actionCode, attrs);
        type.exeAction(param, attrs);
    }

    /**
     * 分页查询数据（支持冗余数据查询）
     *
     * 此方法根据模板ID从MongoDB中查询分页数据，支持复杂的条件查询、排序、全文搜索和业务处理
     * 根据isRedundancy参数决定是否返回冗余数据（更详细的数据信息）
     *
     * @param pageRow 分页查询参数对象，包含页码、页大小、查询条件等
     * @param tableId 模板ID，用于确定要查询的集合（表）名和相关配置
     * @param userVO 用户对象，用于权限控制和业务处理
     * @param isRedundancy 是否返回冗余数据（复杂数据），true为复杂数据，false为简单数据
     * @return Page 分页结果对象，包含查询结果列表、页大小、当前页码和总数
     * @throws BusinessException 业务异常
     */
    @Override
    public Page listPageRedundancy(PageRow<ConditionsVO> pageRow, String tableId, UserVO userVO, boolean isRedundancy) throws BusinessException {
        // 根据模板ID获取对应的MongoDB集合名称
        String tableName = objProcessor.getTableName(tableId);

        // 初始化聚合操作列表，用于构建MongoDB聚合查询管道
        List<AggregationOperation> operations = new ArrayList<>();

        // 根据模板ID查询对象配置信息
        EntitySchema cond = queryByTableId(tableId);

        // 获取分页条件对象
        ConditionsVO conditions = pageRow.getCondition();

        // 解析查询条件，根据属性配置转换条件格式
        objProcessor.parseConditions(conditions, cond.getAttrs());

        // 填充条件配置，根据用户信息定制查询条件
        objProcessor.fillConditionConfig(tableId, conditions, userVO);

        // 将条件对象转换为MongoDB的Criteria条件列表
        List<Criteria> criteriaList = baseMongoDao.condition2CriteriaList(conditions);

        Criteria criteria;
        String likeStr = conditions.getLikeStr();

        // 处理全文搜索条件
        if (!StringUtils.isEmpty(likeStr)) {
            // 为全文搜索创建文本索引
            baseMongoDao.insertTextIndex(tableName);
            Map map = new HashMap(1);
            map.put("$search", likeStr);
            criteria = Criteria.where("$text").is(map);
        } else {
            // 没有全文搜索时创建空的条件对象
            criteria = new Criteria();
        }

        // 将条件列表合并为AND操作的单一条件
        criteria = criteria.andOperator(criteriaList);
        operations.add(Aggregation.match(criteria));

        // 构建排序操作
        try {
            Sort sort;
            List<BaseVO.Sort> bSorts = pageRow.getCondition().getSortConditions();
            if (ObjectUtils.isEmpty(bSorts)) {
                // 如果没有批量排序条件，使用单个排序条件
                BaseVO.Sort bSort = pageRow.getCondition().getSortCondition();
                if (bSort == null) bSort = cond.getSortCondition();
                sort = Sort.by(Sort.Direction.fromString(bSort.getDirection()), bSort.getCode());
            } else {
                // 构建多字段排序
                sort = Sort.by(bSorts.stream().map(m -> new Sort.Order(Sort.Direction.fromString(m.getDirection()), m.getCode())).collect(Collectors.toList()));
            }
            SortOperation sortOperation = Aggregation.sort(sort);
            operations.add(sortOperation);
        } catch (Exception ignore) {
            // 排序异常时继续执行
        }

        // 添加跳过操作（用于分页偏移）
        SkipOperation skipOperation = Aggregation.skip(Long.valueOf(pageRow.getPageNo() * pageRow.getPageSize()));
        operations.add(skipOperation);

        // 添加限制操作（用于分页大小）
        LimitOperation limitOperation = Aggregation.limit(pageRow.getPageSize());
        operations.add(limitOperation);

        // 获取标题配置
        List<EntitySchema.AttrConfig> titleConfig = cond.getTitleConfig();

        // 为非读取属性填充配置信息
        objProcessor.fillNotReadAttrConfig(tableId, titleConfig, userVO);

        // 根据是否需要冗余数据选择不同的请求类型进行字段投影
        objProcessor.projectAttr(operations, titleConfig, cond, isRedundancy ? RequestType.COMPLEX : RequestType.SIMPLE);

        // 创建聚合查询对象，设置选项（允许磁盘使用，最大执行时间10秒）
        Aggregation agg = Aggregation.newAggregation(operations).withOptions(AggregationOptions.builder().allowDiskUse(true).maxTime(Duration.ofSeconds(10)).build());

        // 先进行快速估算总数
        long count = mongoTemplate.estimatedCount(tableName);

        // 如果估算总数不超过50万，则进行精确计数（避免大数据量时计数过慢）
        if (count <= 500000)
            count = mongoTemplate.count(Query.query(criteria).maxTime(Duration.ofSeconds(10)), tableName);

        // 执行聚合查询
        AggregationResults<Document> results = mongoTemplate.aggregate(agg, tableName, Document.class);
        List<Document> list = results.getMappedResults();
        // 返回分页结果
        return new Page<>(list, pageRow.getPageSize(), pageRow.getPageNo() + 1, count);
    }

    @Override
    public Page listPage(PageRow<ConditionsVO> pageRow, String templateId, UserVO userVO) throws BusinessException {
        return listPageRedundancy(pageRow, templateId, userVO, true);
    }

    @Override
    public List list(ConditionsVO conditions, String templateId, UserVO userVO) throws BusinessException {
        EntitySchema cond = queryByTableId(templateId);
        String tableName = objProcessor.getTableName(templateId);
        List<AggregationOperation> operations = new ArrayList<>();
        objProcessor.parseConditions(conditions, cond.getAttrs());
        objProcessor.fillConditionConfig(templateId, conditions, userVO);
        List<Criteria> criteriaList = baseMongoDao.condition2CriteriaList(conditions);
        Criteria criteria;
        String likeStr = conditions.getLikeStr();
        if (!StringUtils.isEmpty(likeStr)) {
            baseMongoDao.insertTextIndex(tableName);
            Map map = new HashMap(1);
            map.put("$search", likeStr);
            criteria = Criteria.where("$text").is(map);
        } else {
            criteria = new Criteria();
        }
        criteria = criteria.andOperator(criteriaList);
        operations.add(Aggregation.match(criteria));
        List<EntitySchema.AttrConfig> titleConfig = cond.getTitleConfig();
        //按时间稀疏
        ConditionsVO.SparesConfig sparesConfig = conditions.getSparesConfig();
        if (sparesConfig != null) {
            String timeSparesField = sparesConfig.getTimeAttrCode();
            String sparesType = sparesConfig.getType();
            List<String> groupFields = sparesConfig.getGroupAttrCode();
            String timeFormat = sparesType.equals("m") ? "%Y-%m-%d %H:%M:00" : "%Y-%m-%d %H:00:00";
            AggregationExpression dateExpression = DateOperators.DateToString
                    .dateOf(timeSparesField)
                    .toString(timeFormat)
                    .withTimezone(
                            DateOperators.Timezone.valueOf("+08")
                    );
            operations.add(Aggregation.addFields().addFieldWithValueOf("valueTimeStr", dateExpression).build());

            List<String> groupFieldList = new ArrayList<>();
            groupFieldList.add("valueTimeStr");
            if (!ObjectUtils.isEmpty(groupFields)) {
                groupFieldList.addAll(groupFields);
            }
            GroupOperation groupOperation = Aggregation.group(groupFieldList.toArray(new String[]{}));
            for (EntitySchema.AttrConfig attrConfig : titleConfig) {
                groupOperation = groupOperation.first(attrConfig.getCode()).as(attrConfig.getCode());
            }
            operations.add(groupOperation);
            SortOperation sortOperation = Aggregation.sort(Sort.by(Sort.Direction.ASC, timeSparesField));
            operations.add(sortOperation);
            operations.add(Aggregation.addFields().addFieldWithValueOf(
                    "valueTime",
                    DateOperators.DateFromString.fromStringOf(groupFieldList.size() == 1 ? "$_id" : "$_id.valueTimeStr").withTimezone(DateOperators.Timezone.valueOf("+08")
                    )
            ).build());
        } else {
            try {
                Sort sort;
                List<BaseVO.Sort> bSorts = conditions.getSortConditions();
                if (ObjectUtils.isEmpty(bSorts)) bSorts = cond.getSortConditions();
                if (ObjectUtils.isEmpty(bSorts)) {
                    BaseVO.Sort bSort = conditions.getSortCondition();
                    if (bSort == null) bSort = cond.getSortCondition();
                    sort = Sort.by(Sort.Direction.fromString(bSort.getDirection()), bSort.getCode());
                } else {
                    sort = Sort.by(bSorts.stream().map(m -> new Sort.Order(Sort.Direction.fromString(m.getDirection()), m.getCode())).collect(Collectors.toList()));
                }
                SortOperation sortOperation = Aggregation.sort(sort);
                operations.add(sortOperation);
            } catch (Exception e) {
            }
            objProcessor.fillNotReadAttrConfig(templateId, titleConfig, userVO);
            objProcessor.projectAttr(operations, titleConfig, cond, RequestType.COMPLEX);
        }
        Aggregation agg = Aggregation.newAggregation(operations).withOptions(AggregationOptions.builder().allowDiskUse(true).maxTime(Duration.ofSeconds(10)).build());
        AggregationResults<Document> results = mongoTemplate.aggregate(agg, tableName, Document.class);
        List<Document> list = results.getMappedResults();
        return list;
    }

    @Override
    public EntitySchema queryByTableId(String id, UserVO user) throws BusinessException {
        Obj obj = baseMongoDao.get(id, Obj.class);
        if (obj == null) {
            throw new BusinessException(ErrCode.OBJ_NOT_EXIST);
        }
        if (ObjectUtils.isEmpty(obj.getSortConditions())&&(obj.getSortCondition() == null || !org.springframework.util.StringUtils.hasText(obj.getSortCondition().getCode())))
            obj.setSortCondition(BasePO.Sort.defaultSort());
        EntitySchema vo = DataUtil.o2o(obj, EntitySchema.class);

        if (user == null) {
            return vo;
        }
        RoleVO roleVO = DataUtil.mergeRole(user.getRole(), user.getSelfRole());
        if (roleVO == null) {
            return vo;
        }
        Map<String, RoleVO.DataConfig> map = roleVO.getDataConfigs();
        if (null == map) {
            return vo;
        }
        RoleVO.DataConfig config = map.get(id);
        if (null == config) {
            return vo;
        }
        List<String> notWriteAttrs = config.getNotWriteAttr();
        if (notWriteAttrs != null) {
            List<EntitySchema.AttrConfig> list = vo.getFormConfig();
            if (list != null) {
                int size = list.size();
                for (int i = size - 1; i >= 0; i--) {
                    EntitySchema.AttrConfig conf = list.get(i);
                    if (notWriteAttrs.contains(conf.getCode())) {
                        list.remove(i);
                    }
                }
            }
        }
        List<String> notReadAttrs = config.getNotReadAttr();
        if (notReadAttrs != null) {
            List<EntitySchema.AttrConfig> list = vo.getTitleConfig();
            if (list != null) {
                int size = list.size();
                for (int i = size - 1; i >= 0; i--) {
                    EntitySchema.AttrConfig conf = list.get(i);
                    if (notReadAttrs.contains(conf.getCode())) {
                        list.remove(i);
                    }
                }
            }
        }
        return vo;
    }

    @Override
    public Long count(ConditionsVO conditions, String templateId) throws BusinessException {
        long count = mongoTemplate.estimatedCount(objProcessor.getTableName(templateId));
        //估算数据超过100万的，不精确查询数量
        if (count > 1000000) return count;
        EntitySchema cond = queryByTableId(templateId);
        objProcessor.parseConditions(conditions, cond.getAttrs());
        objProcessor.fillConditionConfig(templateId, conditions, null);
        List<Criteria> criteriaList = baseMongoDao.condition2CriteriaList(conditions);
        return baseMongoDao.count(new Criteria().andOperator(criteriaList), objProcessor.getTableName(templateId));
    }

    @Override
    public List tree(String templateId, ConditionsVO condition, UserVO userVO) throws BusinessException {
        EntitySchema cond = queryByTableId(templateId);
        EntitySchema.AttrVO attr = objProcessor.getAttr("parent", cond.getAttrs());
        if (StringUtils.isEmpty(attr.getCode())) return list(condition, templateId, userVO);
        String tableName = objProcessor.getTableName(templateId);
        List<AggregationOperation> operations = new ArrayList<>();
        objProcessor.parseConditions(condition, cond.getAttrs());
        objProcessor.fillConditionConfig(templateId, condition, userVO);
        List<Criteria> criteriaList = baseMongoDao.condition2CriteriaList(condition);
        criteriaList.add(Criteria.where("parent").isNull());
        Criteria criteria;
        String likeStr = condition.getLikeStr();
        if (!StringUtils.isEmpty(likeStr)) {
            baseMongoDao.insertTextIndex(tableName);
            Map map = new HashMap(1);
            map.put("$search", likeStr);
            criteria = Criteria.where("$text").is(map);
        } else {
            criteria = new Criteria();
        }
        criteria = criteria.andOperator(criteriaList);
        operations.add(Aggregation.match(criteria));
        GraphLookupOperation operation = Aggregation.graphLookup(tableName)
                .startWith("$_id")
                .connectFrom("_id")
                .connectTo("parent.id")
                .restrict(Criteria.where("isDelete").ne(YesOrNo.YES.ordinal()).and("tenantId").is(condition.getTenantId()))
                .as("children");
        operations.add(operation);
        List<EntitySchema.AttrConfig> titleConfig = cond.getTitleConfig();
        objProcessor.fillNotReadAttrConfig(templateId, titleConfig, userVO);
        objProcessor.projectAttr(operations, titleConfig, cond);
        Aggregation agg = Aggregation.newAggregation(operations).withOptions(AggregationOptions.builder().allowDiskUse(true).maxTime(Duration.ofSeconds(10)).build());
        AggregationResults<NodeVO> results = mongoTemplate.aggregate(agg, tableName, NodeVO.class);
        List<NodeVO> list = results.getMappedResults();
        return list;
    }


    @Override
    public void group(GroupParam param) throws BusinessException {
        WebPageVO cond = new WebPageVO();
        cond.setUri(param.getTemplateId());
        cond = baseMongoDao.queryOne("webPage", WebPageVO.class, cond);
        if (cond == null) throw new BusinessException(ErrCode.PARAMETER_ERROR);
        List<ButtonVO> buttonVOS = cond.getButtons();
        if (!CommonUtil.listIsBlank(buttonVOS)) {
            Map<String, Object> args = buttonVOS.stream().filter(m -> "group".equals(m.getCode())).findFirst().orElse(new ButtonVO()).getArgs();
            if (args == null) throw new BusinessException(ErrCode.PARAMETER_ERROR);
            String groupTemplateId = args.get("groupId").toString();
            String rfField = args.get("rfField").toString();
            EntityData entity = new EntityData();
            entity.put("名称", param.getGroupName() == null ? "分组未命名" : param.getGroupName());
            entity.put("tenantId", param.getTenantId());
            entity.put("createId", param.getUserVO().getId());
            String groupId = insetEntity(groupTemplateId, entity);

            List<ObjectId> ids = param.getEntityIds().stream().map(ObjectId::new).collect(Collectors.toList());
            Criteria where = Criteria.where("_id").in(ids);
            Query query = new Query(where);
            Update update = new Update();
            Document docGroupEntity = new Document();
            docGroupEntity.put("id", new ObjectId(groupId));
            update.set(rfField, docGroupEntity);
            mongoTemplate.updateMulti(query, update, objProcessor.getTableName(param.getTemplateId()));

        }

    }

    @Override
    public Object count(ConditionsVO conditions, String templateId, String attr, UserVO userVO) throws BusinessException {
        String tableName = objProcessor.getTableName(templateId);
        List<AggregationOperation> operations = new ArrayList<>();

        EntitySchema cond = queryByTableId(templateId);
        objProcessor.parseConditions(conditions, cond.getAttrs());
        objProcessor.fillConditionConfig(templateId, conditions, null);
        List<Criteria> criteriaList = baseMongoDao.condition2CriteriaList(conditions);
        Criteria criteria = new Criteria().andOperator(criteriaList);
        operations.add(Aggregation.match(criteria));
        operations.add(Aggregation.group().sum(attr).as("total"));
        operations.add(Aggregation.project("total"));
        AggregationResults<Document> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), tableName, Document.class);
        List<Document> list = results.getMappedResults();
        return CommonUtil.listIsBlank(list) ? 0 : list.get(0).get("total");
    }

    /**
     * 合计
     *
     * @param conditions
     * @param templateId
     * @param attr
     * @param userVO
     * @return
     * @throws BusinessException
     */
    @Override
    public Object summation(ConditionsVO conditions, String templateId, String attr, UserVO userVO) throws BusinessException {
        String tableName = objProcessor.getTableName(templateId);
        EntitySchema cond = queryByTableId(templateId);
        List<AggregationOperation> operations = new ArrayList<>();
        objProcessor.parseConditions(conditions, cond.getAttrs());
        objProcessor.fillConditionConfig(templateId, conditions, userVO);
        List<Criteria> criteriaList = baseMongoDao.condition2CriteriaList(conditions);
        Criteria criteria = new Criteria().andOperator(criteriaList);
        operations.add(Aggregation.match(criteria));
        operations.add(Aggregation.addFields().addFieldWithValue(attr, ConvertOperators.valueOf("$" + attr).convertTo(JsonSchemaObject.Type.DOUBLE)).build());
        operations.add(Aggregation.group()
                .sum(attr).as("total")
                .avg(attr).as("avg")
                .max(attr).as("max")
                .min(attr).as("min")
        );
        AggregationResults<Document> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), tableName, Document.class);
        List<Document> list = results.getMappedResults();
        return CommonUtil.listIsBlank(list) ? null : list.get(0);
    }


}
